home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 5 / Skunkware 5.iso / lib / emacs / 19.22 / lisp / ispell.el < prev    next >
Lisp/Scheme  |  1993-11-07  |  36KB  |  1,008 lines

  1. ;;; ispell.el --- this is the GNU EMACS interface to GNU ISPELL version 4.
  2.  
  3. ;;Copyright (C) 1990, 1991, 1993 Free Software Foundation, Inc.
  4.  
  5. ;; Keywords: wp
  6.  
  7. ;;This file is part of GNU Emacs.
  8. ;;
  9. ;;GNU Emacs is free software; you can redistribute it and/or modify
  10. ;;it under the terms of the GNU General Public License as published by
  11. ;;the Free Software Foundation; either version 2, or (at your option)
  12. ;;any later version.
  13. ;;
  14. ;;GNU Emacs is distributed in the hope that it will be useful,
  15. ;;but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. ;;GNU General Public License for more details.
  18. ;;
  19. ;;You should have received a copy of the GNU General Public License
  20. ;;along with GNU Emacs; see the file COPYING.  If not, write to
  21. ;;the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  22.  
  23. ;;; Commentary:
  24.  
  25. ;; This package provides a graceful interface to ispell, the GNU
  26. ;; spelling checker.
  27.  
  28. ;;; Code:
  29.  
  30. (defvar ispell-have-new-look t
  31.   "Non-nil means use the `-r' option when running `look'.")
  32.  
  33. (defvar ispell-enable-tex-parser nil
  34.   "Non-nil enables experimental TeX parser in Ispell for TeX-mode buffers.")
  35.  
  36. (defvar ispell-process nil "The process running Ispell")
  37. (defvar ispell-next-message nil
  38.   "An integer: where in `*ispell*' buffer to find next message from Ispell.")
  39.  
  40. (defvar ispell-command "ispell"
  41.   "Command for running Ispell.")
  42. (defvar ispell-command-options nil
  43.   "*String (or list of strings) to pass to Ispell as command arguments.
  44. You can use this to specify the name of your private dictionary.
  45. The -S option is always passed to Ispell as the last parameter,
  46. and need not be mentioned here.")
  47.  
  48. (defvar ispell-look-command "look"
  49.   "*Command for running look.")
  50.  
  51. ;Each marker in this list points to the start of a word that
  52. ;ispell thought was bad last time it did the :file command.
  53. ;Notice that if the user accepts or inserts a word into his
  54. ;private dictionary, then some "good" words will be on the list.
  55. ;We would like to deal with this by looking up the words again just before
  56. ;presenting them to the user, but that is too slow on machines
  57. ;without the select system call.  Therefore, see the variable
  58. ;ispell-recently-accepted.
  59. (defvar ispell-bad-words nil
  60.   "A list of markers reflecting the output of the Ispell `:file' command.")
  61.  
  62. ;list of words that the user has accepted, but that might still
  63. ;be on the bad-words list
  64. (defvar ispell-recently-accepted nil)
  65.  
  66. ;; Non-nil means we have started showing an alternatives window.
  67. ;; This is the window config from before then.
  68. (defvar ispell-window-configuration nil)
  69.  
  70. ;t when :dump command needed
  71. (defvar ispell-dump-needed nil)
  72.  
  73. (defun ispell-flush-bad-words ()
  74.   (while ispell-bad-words
  75.     (if (markerp (car ispell-bad-words))
  76.         (set-marker (car ispell-bad-words) nil))
  77.     (setq ispell-bad-words (cdr ispell-bad-words)))
  78.   (setq ispell-recently-accepted nil))
  79.  
  80. (defun kill-ispell ()
  81.   "Kill the Ispell process.
  82. Any changes in your private dictionary
  83. that have not already been dumped will be lost."
  84.   (interactive)
  85.   (if ispell-process
  86.       (delete-process ispell-process))
  87.   (setq ispell-process nil)
  88.   (ispell-flush-bad-words))
  89.  
  90. (put 'ispell-startup-error 'error-conditions
  91.      '(ispell-startup-error error))
  92. (put 'ispell-startup-error 'error-message
  93.      "Problem starting ispell - see buffer *ispell*")
  94.  
  95. ;; Start an ispell subprocess; check the version; and display the greeting.
  96.  
  97. (defun start-ispell ()
  98.   (message "Starting ispell ...")
  99.   (let ((buf (get-buffer "*ispell*")))
  100.     (if buf
  101.     (kill-buffer buf)))
  102.   (condition-case err
  103.       (setq ispell-process
  104.         (apply 'start-process "ispell" "*ispell*" ispell-command
  105.            (append (if (listp ispell-command-options)
  106.                    ispell-command-options
  107.                  (list ispell-command-options))
  108.                '("-S"))))
  109.     (file-error (signal 'ispell-startup-error nil)))
  110.   (process-kill-without-query ispell-process)
  111.   (buffer-disable-undo (process-buffer ispell-process))
  112.   (accept-process-output ispell-process)
  113.   (let (last-char)
  114.     (save-excursion
  115.       (set-buffer (process-buffer ispell-process))
  116.       (bury-buffer (current-buffer))
  117.       (setq last-char (- (point-max) 1))
  118.       (while (not (eq (char-after last-char) ?=))
  119.     (cond ((not (eq (process-status ispell-process) 'run))
  120.            (kill-ispell)
  121.            (signal 'ispell-startup-error nil)))
  122.     (accept-process-output ispell-process)
  123.     (setq last-char (- (point-max) 1)))
  124.       (goto-char (point-min))
  125.       (let ((greeting (read (current-buffer))))
  126.     (if (not (= (car greeting) 1))
  127.         (error "Bad ispell version: wanted 1, got %d" (car greeting)))
  128.     (message (car (cdr greeting))))
  129.       (delete-region (point-min) last-char))))
  130.   
  131. ;; Make sure ispell is ready for a command.
  132. ;; Leaves buffer set to *ispell*, point at '='.
  133.  
  134. (defun ispell-sync (intr)
  135.   (if (or (null ispell-process)
  136.       (not (eq (process-status ispell-process) 'run)))
  137.       (start-ispell))
  138.   (if intr
  139.       (interrupt-process ispell-process))
  140.   (let (last-char)
  141.     (set-buffer (process-buffer ispell-process))
  142.     (bury-buffer (current-buffer))
  143.     (setq last-char (- (point-max) 1))
  144.     (while (not (eq (char-after last-char) ?=))
  145.       (accept-process-output ispell-process)
  146.       (setq last-char (- (point-max) 1)))
  147.     (goto-char last-char)))
  148.  
  149. ;; Send a command to ispell.  Choices are:
  150. ;; 
  151. ;; WORD        Check spelling of WORD.  Result is
  152. ;; 
  153. ;;             nil               not found
  154. ;;             t               spelled ok
  155. ;;             list of strings           near misses
  156. ;; 
  157. ;; :file FILENAME    scan the named file, and print the file offsets of
  158. ;;         any misspelled words
  159. ;; 
  160. ;; :insert WORD    put word in private dictionary
  161. ;; 
  162. ;; :accept WORD    don't complain about word any more this session
  163. ;; 
  164. ;; :dump        write out the current private dictionary, if necessary.
  165. ;; 
  166. ;; :reload        reread `~/ispell.words'
  167. ;; 
  168. ;; :tex
  169. ;; :troff
  170. ;; :generic    set type of parser to use when scanning whole files
  171.  
  172. (defun ispell-cmd (&rest strings)
  173.   (save-excursion
  174.     (ispell-sync t)
  175.     (set-buffer (process-buffer ispell-process))
  176.     (bury-buffer (current-buffer))
  177.     (erase-buffer)
  178.     (setq ispell-next-message (point-min))
  179.     (while strings
  180.       (process-send-string ispell-process (car strings))
  181.       (setq strings (cdr strings)))
  182.     (process-send-string ispell-process "\n")
  183.     (accept-process-output ispell-process)
  184.     (ispell-sync nil)))
  185.  
  186. (defun ispell-dump ()
  187.   (cond (ispell-dump-needed
  188.      (setq ispell-dump-needed nil)
  189.      (ispell-cmd ":dump"))))
  190.  
  191. (defun ispell-insert (word)
  192.   (ispell-cmd ":insert " word)
  193.   (if ispell-bad-words
  194.       (setq ispell-recently-accepted (cons word ispell-recently-accepted)))
  195.   (setq ispell-dump-needed t))
  196.  
  197. (defun ispell-accept (word)
  198.   (ispell-cmd ":accept " word)
  199.   (if ispell-bad-words
  200.       (setq ispell-recently-accepted (cons word ispell-recently-accepted))))
  201.  
  202. ;; Return the next message sent by the Ispell subprocess.
  203.  
  204. (defun ispell-next-message ()
  205.   (save-excursion
  206.     (set-buffer (process-buffer ispell-process))
  207.     (bury-buffer (current-buffer))
  208.     (save-restriction
  209.       (goto-char ispell-next-message)
  210.       (narrow-to-region (point)
  211.                         (progn (forward-sexp 1) (point)))
  212.       (setq ispell-next-message (point))
  213.       (goto-char (point-min))
  214.       (read (current-buffer)))))
  215.  
  216. (defun ispell-tex-buffer-p ()
  217.   (memq major-mode '(plain-TeX-mode LaTeX-mode)))
  218.  
  219. (defvar ispell-menu-map (make-sparse-keymap "Spell"))
  220. (defalias 'ispell-menu-map ispell-menu-map)
  221.  
  222. (define-key ispell-menu-map [ispell-complete-word-interior-frag]
  223.   '("Complete Interior Fragment" . ispell-complete-word-interior-frag))
  224.  
  225. (define-key ispell-menu-map [ispell-complete-word]
  226.   '("Complete Word" . ispell-complete-word))
  227.  
  228. (define-key ispell-menu-map [reload-ispell]
  229.   '("Reload Dictionary" . reload-ispell))
  230.  
  231. (define-key ispell-menu-map [ispell-next]
  232.   '("Continue Check" . ispell-next))
  233.  
  234. (define-key ispell-menu-map [ispell-message]
  235.   '("Check Message" . ispell-message))
  236.  
  237. (define-key ispell-menu-map [ispell-region]
  238.   '("Check Region" . ispell-region))
  239.  
  240. (define-key ispell-menu-map [ispell-buffer]
  241.   '("Check Buffer" . ispell))
  242.  
  243. (define-key ispell-menu-map [ispell-word]
  244.   '("Check Word" . ispell-word))
  245.  
  246. ;;;###autoload
  247. (defun ispell (&optional buf start end)
  248.   "Run Ispell over current buffer's visited file.
  249. First the file is scanned for misspelled words, then Ispell
  250. enters a loop with the following commands for every misspelled word:
  251.  
  252. DIGIT    Near miss selector.  If the misspelled word is close to
  253.     some words in the dictionary, they are offered as near misses.
  254. r    Replace.  Replace the word with a string you type.  Each word
  255.     of your new string is also checked.
  256. i    Insert.  Insert this word in your private dictionary (kept in
  257.     `$HOME/ispell.words').
  258. a    Accept.  Accept this word for the rest of this editing session,
  259.      but don't put it in your private dictionary.
  260. l    Lookup.  Look for a word in the dictionary by fast binary
  261.     search, or search for a regular expression in the dictionary
  262.     using grep.
  263. SPACE    Accept the word this time, but complain if it is seen again.
  264. q, \\[keyboard-quit]    Leave the command loop.  You can come back later with \\[ispell-next]."
  265.   (interactive)
  266.   (if (null start)
  267.       (setq start 0))
  268.   (if (null end)
  269.       (setq end 0))
  270.  
  271.   (if (null buf)
  272.       (setq buf (current-buffer)))
  273.   (setq buf (get-buffer buf))
  274.   (if (null buf)
  275.       (error "Can't find buffer"))
  276.   ;; Deactivate the mark, because we'll do it anyway if we change something,
  277.   ;; and a region highlight while in the Ispell loop is distracting.
  278.   (deactivate-mark)
  279.   (save-excursion
  280.     (set-buffer buf)
  281.     (let ((filename buffer-file-name)
  282.       (delete-temp nil))
  283.       (unwind-protect
  284.       (progn
  285.         (cond ((or (null filename)
  286.                (find-file-name-handler buffer-file-name))
  287.            (setq filename (make-temp-name "/usr/tmp/ispell"))
  288.            (setq delete-temp t)
  289.            (write-region (point-min) (point-max) filename))
  290.           ((and (buffer-modified-p buf)
  291.             (y-or-n-p (format "Save file %s? " filename)))
  292.            (save-buffer)))
  293.         (message "Ispell scanning file...")
  294.         (if (and ispell-enable-tex-parser
  295.              (ispell-tex-buffer-p))
  296.         (ispell-cmd ":tex")
  297.           (ispell-cmd ":generic"))
  298.         (ispell-cmd (format ":file %s %d %d" filename start end)))
  299.     (if delete-temp
  300.         (condition-case ()
  301.         (delete-file filename)
  302.           (file-error nil)))))
  303.     (message "Parsing ispell output ...")
  304.     (ispell-flush-bad-words)
  305.     (let (pos bad-words)
  306.       (while (numberp (setq pos (ispell-next-message)))
  307.     ;;ispell may check the words on the line following the end
  308.     ;;of the region - therefore, don't record anything out of range
  309.     (if (or (= end 0)
  310.         (< pos end))
  311.         (setq bad-words (cons (set-marker (make-marker) (+ pos 1))
  312.                   bad-words))))
  313.       (setq bad-words (cons pos bad-words))
  314.       (setq ispell-bad-words (nreverse bad-words))))
  315.   (cond ((not (markerp (car ispell-bad-words)))
  316.      (setq ispell-bad-words nil)
  317.      (message "No misspellings.")
  318.      t)
  319.     (t
  320.      (message "Ispell parsing done.")
  321.      (ispell-next))))
  322.  
  323. ;;;###autoload
  324. (defalias 'ispell-buffer 'ispell)
  325.  
  326. (defun ispell-next ()
  327.   "Resume command loop for most recent Ispell command.
  328. Return value is t unless exit is due to typing `q'."
  329.   (interactive)
  330.   (setq ispell-window-configuration nil)
  331.   (prog1
  332.       (unwind-protect
  333.       (catch 'ispell-quit
  334.         ;; There used to be a save-excursion here,
  335.         ;; but that was annoying: it's better if point doesn't move
  336.         ;; when you type q.
  337.         (let (next)
  338.           (while (markerp (setq next (car ispell-bad-words)))
  339.         (switch-to-buffer (marker-buffer next))
  340.         (push-mark)
  341.         (ispell-point next "at saved position.")
  342.         (setq ispell-bad-words (cdr ispell-bad-words))
  343.         (set-marker next nil)))
  344.         t)
  345.     (if ispell-window-configuration
  346.         (set-window-configuration ispell-window-configuration))
  347.     (cond ((null ispell-bad-words)
  348.            (error "Ispell has not yet been run."))
  349.           ((markerp (car ispell-bad-words))
  350.            (message (substitute-command-keys
  351.                "Type \\[ispell-next] to continue.")))
  352.           ((eq (car ispell-bad-words) nil)
  353.            (setq ispell-bad-words nil)
  354.            (message "No more misspellings (but checker was interrupted.)"))
  355.           ((eq (car ispell-bad-words) t)
  356.            (setq ispell-bad-words nil)
  357.            (message "Ispell done."))
  358.           (t
  359.            (setq ispell-bad-words nil)
  360.            (message "Bad ispell internal list"))))
  361.     (ispell-dump)))
  362.  
  363. ;;;###autoload
  364. (defun ispell-word (&optional resume)
  365.   "Check the spelling of the word under the cursor.
  366. See the command `ispell' for more information.
  367. With a prefix argument, resume handling of the previous Ispell command."
  368.   (interactive "P")
  369.   (if resume
  370.       (ispell-next)
  371.     (condition-case err
  372.     (catch 'ispell-quit
  373.       (save-window-excursion
  374.         (ispell-point (point) "at point."))
  375.       (ispell-dump))
  376.       (ispell-startup-error
  377.        (cond ((y-or-n-p "Problem starting ispell, use old-style spell instead? ")
  378.           (load-library "spell")
  379.           (define-key esc-map "$" 'spell-word)
  380.           (spell-word)))))))
  381. ;;;###autoload
  382. (define-key esc-map "$" 'ispell-word)
  383.  
  384. ;;;###autoload
  385. (defun ispell-region (start &optional end)
  386.   "Check the spelling for all of the words in the region."
  387.   (interactive "r")
  388.   (ispell (current-buffer) start end))
  389.  
  390. (defun ispell-letterp (c)
  391.   (and c
  392.        (or (and (>= c ?A) (<= c ?Z))
  393.        (and (>= c ?a) (<= c ?z))
  394.        (>= c 128))))
  395.  
  396. (defun ispell-letter-or-quotep (c)
  397.   (and c
  398.        (or (and (>= c ?A) (<= c ?Z))
  399.        (and (>= c ?a) (<= c ?z))
  400.        (= c ?')
  401.        (>= c 128))))
  402.  
  403. (defun ispell-find-word-start ()
  404.   ;;backward to a letter
  405.   (if (not (ispell-letterp (char-after (point))))
  406.       (while (and (not (bobp))
  407.           (not (ispell-letterp (char-after (- (point) 1)))))
  408.     (backward-char)))
  409.   ;;backward to beginning of word
  410.   (while (ispell-letter-or-quotep (char-after (- (point) 1)))
  411.     (backward-char))
  412.   (skip-chars-forward "'"))
  413.  
  414. (defun ispell-find-word-end ()
  415.   (while (ispell-letter-or-quotep (char-after (point)))
  416.     (forward-char))
  417.   (skip-chars-backward "'"))
  418.  
  419. (defun ispell-next-word ()
  420.   (while (and (not (eobp))
  421.           (not (ispell-letterp (char-after (point)))))
  422.     (forward-char)))
  423.  
  424. ;if end is nil, then do one word at start
  425. ;otherwise, do all words from the beginning of the word where
  426. ;start points, to the end of the word where end points
  427. (defun ispell-point (start message)
  428.   (let ((wend (make-marker))
  429.     rescan
  430.     end)
  431.     ;; There used to be a save-excursion here,
  432.     ;; but that was annoying: it's better if point doesn't move
  433.     ;; when you type q.
  434.     (goto-char start)
  435.     (ispell-find-word-start)        ;find correct word start
  436.     (setq start (point-marker))
  437.     (ispell-find-word-end)        ;now find correct end
  438.     (setq end (point-marker))
  439.     ;; Do nothing if we don't find a word.
  440.     (if (< start end)
  441.     (while (< start end)
  442.       (goto-char start)
  443.       (ispell-find-word-end)    ;find end of current word
  444.                     ;could be before 'end' if
  445.                     ;user typed replacement
  446.                     ;that is more than one word
  447.       (set-marker wend (point))
  448.       (setq rescan nil)
  449.       (setq word (buffer-substring start wend))
  450.       (cond ((ispell-still-bad word)
  451. ;;; This just causes confusion. -- rms.
  452. ;;;         (goto-char start)
  453. ;;;         (sit-for 0)
  454.          (message (format "Ispell checking %s" word))
  455.          (ispell-cmd word)
  456.          (let ((message (ispell-next-message)))
  457.            (cond ((eq message t)
  458.               (message "%s: ok" word))
  459.              ((or (null message)
  460.                   (consp message))
  461.               (setq rescan
  462.                 (ispell-command-loop word start wend message)))
  463.              (t
  464.               (error "unknown ispell response %s" message))))))
  465.       (cond ((null rescan)
  466.          (goto-char wend)
  467.          (ispell-next-word)
  468.          (set-marker start (point))))))
  469.     ;;clear the choices buffer; otherwise it's hard for the user to tell
  470.     ;;when we get back to the command loop
  471.     (let ((buf (get-buffer "*ispell choices*")))
  472.       (cond (buf
  473.          (set-buffer buf)
  474.          (erase-buffer))))
  475.     (set-marker start nil)
  476.     (set-marker end nil)
  477.     (set-marker wend nil)))
  478.   
  479. (defun ispell-still-bad (word)
  480.   (let ((words ispell-recently-accepted)
  481.     (ret t)
  482.     (case-fold-search t))
  483.     (while words
  484.       (cond ((eq (string-match (car words) word) 0)
  485.          (setq ret nil)
  486.          (setq words nil)))
  487.       (setq words (cdr words)))
  488.     ret))
  489.  
  490. (defun ispell-show-choices (word message first-line)
  491.   ;;if there is only one window on the frame, make the ispell
  492.   ;;messages winow be small.  otherwise just use the other window
  493.   (let* ((selwin (selected-window))
  494.      (resize (eq selwin (next-window)))
  495.      (buf (get-buffer-create "*ispell choices*"))
  496.      w)
  497.     (or ispell-window-configuration
  498.     (setq ispell-window-configuration (current-window-configuration)))
  499.     (setq w (display-buffer buf))
  500.     (buffer-disable-undo buf)
  501.     (if resize
  502.     (unwind-protect
  503.         (progn
  504.           (select-window w)
  505.           (enlarge-window (- 6 (window-height w))))
  506.       (select-window selwin)))
  507.     (save-excursion
  508.       (set-buffer buf)
  509.       (bury-buffer buf)
  510.       (set-window-point w (point-min))
  511.       (set-window-start w (point-min))
  512.       (erase-buffer)
  513.       (insert first-line "\n")
  514.       (insert
  515.        "SPC skip; A accept; I insert; DIGIT select; R replace; \
  516. L lookup; Q quit\n")
  517.       (cond ((not (null message))
  518.          (let ((i 0))
  519.            (while (< i 3)
  520.          (let ((j 0))
  521.            (while (< j 3)
  522.              (let* ((n (+ (* j 3) i))
  523.                 (choice (nth n message)))
  524.                (cond (choice
  525.                   (let ((str (format "%d %s" n choice)))
  526.                 (insert str)
  527.                 (insert-char ?  (- 20 (length str)))))))
  528.              (setq j (+ j 1))))
  529.          (insert "\n")
  530.          (setq i (+ i 1)))))))))
  531.  
  532. (defun ispell-command-loop (word start end message)
  533.   (let ((flag t)
  534.     (rescan nil)
  535.     first-line)
  536.     (if (null message)
  537.     (setq first-line (concat "No near misses for '" word "'"))
  538.       (setq first-line (concat "Near misses for '" word "'")))
  539.     (while flag
  540.       (ispell-show-choices word message first-line)
  541.       (message "Ispell command: ")
  542.       (undo-boundary)
  543.       (let ((c (downcase (read-char)))
  544.         replacement)
  545.     (cond ((and (>= c ?0)
  546.             (<= c ?9)
  547.             (setq replacement (nth (- c ?0) message)))
  548.            (ispell-replace start end replacement)
  549.            (setq flag nil))
  550.           ((= c ?q)
  551.            (throw 'ispell-quit nil))
  552.           ((= c (nth 3 (current-input-mode)))
  553.            (keyboard-quit))
  554.           ((= c ? )
  555.            (setq flag nil))
  556.           ((= c ?r)
  557.            (ispell-replace start end (read-string "Replacement: "))
  558.            (setq rescan t)
  559.            (setq flag nil))
  560.           ((= c ?i)
  561.            (ispell-insert word)
  562.            (setq flag nil))
  563.           ((= c ?a)
  564.            (ispell-accept word)
  565.            (setq flag nil))
  566.           ((= c ?l)
  567.            (let ((val (ispell-do-look word)))
  568.          (setq first-line (car val))
  569.          (setq message (cdr val))))
  570.           ((= c ??)
  571.            (message
  572.         "Type 'C-h d ispell' to the emacs main loop for more help")
  573.            (sit-for 2))
  574.           (t
  575.            (message "Bad ispell command")
  576.            (sit-for 2)))))
  577.     rescan))
  578.  
  579. (defun ispell-do-look (bad-word)
  580.   (let (regex buf words)
  581.     (cond ((null ispell-have-new-look)
  582.        (setq regex (read-string "Lookup: ")))
  583.       (t
  584.        (setq regex (read-string "Lookup (regex): " "^"))))
  585.     (setq buf (get-buffer-create "*ispell look*"))
  586.     (save-excursion
  587.       (set-buffer buf)
  588.       (delete-region (point-min) (point-max))
  589.       (if ispell-have-new-look
  590.       (call-process ispell-look-command nil buf nil "-r" regex)
  591.     (call-process ispell-look-command nil buf nil regex))
  592.       (goto-char (point-min))
  593.       (forward-line 10)
  594.       (delete-region (point) (point-max))
  595.       (goto-char (point-min))
  596.       (while (not (= (point-min) (point-max)))
  597.     (end-of-line)
  598.     (setq words (cons (buffer-substring (point-min) (point)) words))
  599.     (forward-line)
  600.     (delete-region (point-min) (point)))
  601.       (kill-buffer buf)
  602.       (cons (format "Lookup '%s'" regex)
  603.         (reverse words)))))
  604.     
  605. (defun ispell-replace (start end new)
  606.   (goto-char start)
  607.   (insert new)
  608.   (delete-region (point) end))
  609.  
  610. (defun reload-ispell ()
  611.   "Tell Ispell to re-read your private dictionary."
  612.   (interactive)
  613.   (ispell-cmd ":reload"))
  614.  
  615. (defun batch-make-ispell ()
  616.   (byte-compile-file "ispell.el")
  617.   (find-file "ispell.texinfo")
  618.   (let ((old-dir default-directory)
  619.     (default-directory "/tmp"))
  620.     (texinfo-format-buffer))
  621.   (Info-validate)
  622.   (if (get-buffer " *problems in info file*")
  623.       (kill-emacs 1))
  624.   (write-region (point-min) (point-max) "ispell.info"))
  625.  
  626. ;;;; ispell-complete-word
  627.  
  628. ;;; Brief Description:
  629. ;;; Complete word fragment at point using dictionary and replace with full
  630. ;;; word.  Expansion done in current buffer like lisp-complete-symbol.
  631. ;;; Completion of interior word fragments possible with prefix argument.
  632.  
  633. ;;; Known Problem: 
  634. ;;; Does not use private dictionary because GNU `look' does not use it.  It
  635. ;;; would be nice if GNU `look' took standard input; this would allow gzip'ed
  636. ;;; dictionaries to be used.  GNU `look' also has a bug, see
  637. ;;; `ispell-gnu-look-still-broken-p'.
  638.  
  639. ;;; Motivation: 
  640. ;;; The `l', "regular expression look up", keymap option of ispell-word
  641. ;;; (ispell-do-look) can only be run after finding a misspelled word.  So
  642. ;;; ispell-do-look can not be used to look for words starting with `cat' to
  643. ;;; find `catechetical' since `cat' is a correctly spelled word.  Furthermore,
  644. ;;; ispell-do-look does not return the entire list returned by `look'.
  645. ;;;  
  646. ;;; ispell-complete-word allows you to get a completion list from the system
  647. ;;; dictionary and expand a word fragment at the current position in a buffer.
  648. ;;; These examples assume ispell-complete-word is bound to M-TAB as it is in
  649. ;;; text-mode; the `Complete Word' and `Complete Interior Fragment' entries of
  650. ;;; the "Spell" submenu under the "Edit" menu may also be used instead of
  651. ;;; M-TAB and C-u M-TAB, respectively.
  652. ;;;
  653. ;;;   EXAMPLE 1: The word `Saskatchewan' needs to be spelled.  The user may
  654. ;;;   type `Sas' and hit M-TAB and a completion list will be built using the
  655. ;;;   shell command `look' and displayed in the *Completions* buffer:
  656. ;;;
  657. ;;;        Possible completions are:
  658. ;;;        sash                               sashay
  659. ;;;        sashayed                           sashed
  660. ;;;        sashes                             sashimi
  661. ;;;        Saskatchewan                       Saskatoon
  662. ;;;        sass                               sassafras
  663. ;;;        sassier                            sassing
  664. ;;;        sasswood                           sassy
  665. ;;;
  666. ;;;   By viewing this list the user will hopefully be motivated to insert the
  667. ;;;   letter `k' after the `sas'.  When M-TAB is hit again the word `Saskat'
  668. ;;;   will be inserted in place of `sas' (note case) since this is a unique
  669. ;;;   substring completion.  The narrowed completion list can be viewed with
  670. ;;;   another M-TAB
  671. ;;;
  672. ;;;        Possible completions are:
  673. ;;;        Saskatchewan                       Saskatoon
  674. ;;;
  675. ;;;   Inserting the letter `c' and hitting M-TAB will narrow the completion
  676. ;;;   possibilities to just `Saskatchewan' and this will be inserted in the
  677. ;;;   buffer.  At any point the user may click the mouse on a completion to
  678. ;;;   select it.
  679. ;;;
  680. ;;;   EXAMPLE 2: The user has typed `Sasaquane' and M-$ (ispell-word) gives no
  681. ;;;   "near-misses" in which case you back up to `Sas' and hit M-TAB and find
  682. ;;;   the correct word as above.  The `Sas' will be replaced by `Saskatchewan'
  683. ;;;   and the remaining word fragment `aquane' can be deleted.
  684. ;;;
  685. ;;;   EXAMPLE 3: If a version of `look' is used that supports regular
  686. ;;;   expressions, then `ispell-have-new-look' should be t (its default) and
  687. ;;;   interior word fragments may also be used for the search.  The word
  688. ;;;   `pneumonia' needs to be spelled.  The user can only remember the
  689. ;;;   interior fragment `mon' in which case `C-u M-TAB' on `mon' gives a list
  690. ;;;   of all words containing the interior word fragment `mon'.  Typing `p'
  691. ;;;   and M-TAB will narrow this list to all the words starting with `p' and
  692. ;;;   containing `mon' from which `pneumonia' can be found as above.
  693.  
  694. ;;; The user-defined variables are:
  695. ;;;
  696. ;;;  ispell-look-command
  697. ;;;  ispell-look-dictionary
  698. ;;;  ispell-gnu-look-still-broken-p
  699.  
  700. ;;; Algorithm (some similarity to lisp-complete-symbol):
  701. ;;;  
  702. ;;; * call-process on command ispell-look-command (default: "look") to find
  703. ;;;   words in ispell-look-dictionary matching `string' (or `regexp' if 
  704. ;;;   ispell-have-new-look is t).  Parse output and store results in 
  705. ;;;   ispell-lookup-completions-alist.
  706. ;;; 
  707. ;;; * Build completion list using try-completion and `string'
  708. ;;; 
  709. ;;; * Replace `string' in buffer with matched common substring completion.
  710. ;;; 
  711. ;;; * Display completion list only if there is no matched common substring.
  712. ;;; 
  713. ;;; * Rebuild ispell-lookup-completions-alist, on a next call, only when
  714. ;;;   beginning of word fragment has changed.
  715. ;;;  
  716. ;;; * Interior fragments searches are performed similarly with the exception
  717. ;;;   that the entire fragment at point is initially removed from the buffer,
  718. ;;;   the STRING passed to try-completion and all-completions is just "" and
  719. ;;;   not the interior fragment; this allows all completions containing the
  720. ;;;   interior fragment to be shown.  The location in the buffer is stored to
  721. ;;;   decide whether future completion narrowing of the current list should be
  722. ;;;   done or if a new list should be built.  See interior fragment example
  723. ;;;   above.
  724. ;;;
  725. ;;; * Robust searches are done using a `look' with -r (regular expression) 
  726. ;;;   switch if ispell-have-new-look is t.
  727.  
  728. ;;;; User-defined variables.
  729.  
  730. (defvar ispell-look-dictionary nil
  731.   "*If non-nil then spelling dictionary as string for `ispell-complete-word'.
  732. Overrides default dictionary file such as \"/usr/dict/words\" or GNU look's
  733. \"${prefix}/lib/ispell/ispell.words\"")
  734.  
  735. (defvar ispell-gnu-look-still-broken-p nil
  736.   "*t if GNU look -r can give different results with and without trialing `.*'.
  737. Example: `look -dfr \"^ya\" foo' returns nothing, while `look -dfr \"^ya.*\" foo'
  738. returns `yacc', where `foo' is a dictionary file containing the three lines
  739.  
  740.    y
  741.    y's
  742.    yacc
  743.  
  744. Both commands should return `yacc'.  If `ispell-complete-word' erroneously
  745. states that no completions exist for a string, then setting this variable to t
  746. will help find those completions.")
  747.  
  748. ;;;; Internal variables.
  749.  
  750. ;;; Possible completions for last word fragment.
  751. (defvar ispell-lookup-completions-alist nil)
  752.  
  753. ;;; Last word fragment processed by `ispell-complete-word'.
  754. (defvar ispell-lookup-last-word nil)
  755.  
  756. ;;; Buffer local variables.
  757.  
  758. ;;; Value of interior-frag in last call to `ispell-complete-word'.
  759. (defvar ispell-lookup-last-interior-p nil)
  760. (make-variable-buffer-local 'ispell-lookup-last-interior-p)
  761. (put 'ispell-lookup-last-interior-p 'permanent-local t)
  762.  
  763. ;;; Buffer position in last call to `ispell-complete-word'.
  764. (defvar ispell-lookup-last-bow nil)
  765. (make-variable-buffer-local 'ispell-lookup-last-bow)
  766. (put 'ispell-lookup-last-bow 'permanent-local t)
  767.  
  768. ;;;; Interactive functions.
  769. ;;;###autoload
  770. (defun ispell-complete-word (&optional interior-frag)
  771.   "Complete word using letters at point to word beginning using `look'.
  772. With optional argument INTERIOR-FRAG, word fragment at point is assumed to be
  773. an interior word fragment in which case `ispell-have-new-look' should be t.
  774. See also `ispell-look-dictionary' and `ispell-gnu-look-still-broken-p'."
  775.  
  776.   (interactive "P")
  777.  
  778.   ;; `look' must support regexp expressions in order to perform an interior
  779.   ;; fragment search.
  780.   (if (and interior-frag (not ispell-have-new-look))
  781.       (error (concat "Sorry `ispell-have-new-look' is nil.  "
  782.                      "You also will need GNU Ispell's `look'.")))
  783.  
  784.   (let* ((completion-ignore-case t)
  785.  
  786.          ;; Get location of beginning of word fragment.
  787.          (bow (save-excursion (skip-chars-backward "a-zA-Z'") (point)))
  788.  
  789.          ;; Get the string to look up.
  790.          (string (buffer-substring bow (point)))
  791.  
  792.          ;; Get regexp for which we search and, if necessary, an interior word
  793.          ;; fragment.
  794.          (regexp (if interior-frag
  795.                      (concat "^.*" string ".*")
  796.                    ;; If possible use fast binary search: no trailing `.*'.
  797.                    (concat "^" string
  798.                            (if ispell-gnu-look-still-broken-p ".*"))))
  799.  
  800.          ;; We want all completions for case of interior fragments so set
  801.          ;; prefix to an empty string.
  802.          (prefix (if interior-frag "" string))
  803.  
  804.          ;; Are we continuing from a previous interior fragment search?
  805.          ;; Check last value of interior-word and if the point has moved.
  806.          (continuing-an-interior-frag-p
  807.           (and ispell-lookup-last-interior-p
  808.                (equal ispell-lookup-last-bow bow)))
  809.  
  810.          ;; Are we starting a unique word fragment search?  Always t for
  811.          ;; interior word fragment search.
  812.          (new-unique-string-p
  813.           (or interior-frag (null ispell-lookup-last-word)
  814.               (let ((case-fold-search t))
  815.                 ;; Can we locate last word fragment as a substring of current
  816.                 ;; word fragment?  If the last word fragment is larger than
  817.                 ;; the current string then we will have to rebuild the list
  818.                 ;; later.
  819.                 (not (string-match
  820.                       (concat "^" ispell-lookup-last-word) string)))))
  821.  
  822.          completion)
  823.  
  824.     ;; Check for perfect completion already.  That is, maybe the user has hit
  825.     ;; M-x ispell-complete-word one too many times?
  826.     (if (string-equal string "")
  827.         (if (string-equal (concat ispell-lookup-last-word " ")
  828.                           (buffer-substring
  829.                            (save-excursion (forward-word -1) (point)) (point)))
  830.             (error "Perfect match...still.  Please move on.")
  831.           (error "No word fragment at point.")))
  832.  
  833.     ;; Create list of words from system dictionary starting with `string' if
  834.     ;; new string and not continuing from a previous interior fragment search.
  835.     (if (and (not continuing-an-interior-frag-p) new-unique-string-p)
  836.         (setq ispell-lookup-completions-alist
  837.               (ispell-lookup-build-list string regexp)))
  838.  
  839.     ;; Check for a completion of `string' in the list and store `string' and
  840.     ;; other variables for the next call.
  841.     (setq completion (try-completion prefix ispell-lookup-completions-alist)
  842.           ispell-lookup-last-word string
  843.           ispell-lookup-last-interior-p interior-frag
  844.           ispell-lookup-last-bow bow)
  845.  
  846.     ;; Test the completion status.
  847.     (cond
  848.  
  849.      ;; * Guess is a perfect match.
  850.      ((eq completion t)
  851.       (insert " ")
  852.       (message "Perfect match."))
  853.  
  854.      ;; * No possibilities.
  855.      ((null completion)
  856.       (message "Can't find completion for \"%s\"" string)
  857.       (beep))
  858.  
  859.      ;; * Replace string fragment with matched common substring completion.
  860.      ((and (not (string-equal completion ""))
  861.            ;; Fold case so a completion list is built when `string' and common
  862.            ;; substring differ only in case.
  863.            (let ((case-fold-search t))
  864.              (not (string-match (concat "^" completion "$") string))))
  865.       (search-backward string bow)
  866.       (replace-match completion nil t) ; FIXEDCASE doesn't work? or LITERAL?
  867.       (message "Proposed unique substring.  Repeat for completions list."))
  868.  
  869.      ;; * String is a common substring completion already.  Make list.
  870.      (t
  871.       (message "Making completion list...")
  872.       (if (string-equal completion "") (delete-region bow (point)))
  873.       (let ((list (all-completions prefix ispell-lookup-completions-alist)))
  874.         (with-output-to-temp-buffer " *Completions*"
  875.           (display-completion-list list)))
  876.       (message "Making completion list...done")))))
  877.  
  878. ;;;###autoload
  879. (defun ispell-complete-word-interior-frag ()
  880.   "Runs `ispell-complete-word' with a non-nil INTERIOR-FRAG.
  881. A completion list is built for word fragment at point which is assumed to be
  882. an interior word fragment.  `ispell-have-new-look' should be t."
  883.   (interactive)
  884.   (ispell-complete-word t))
  885.  
  886. ;;;; Internal Function.
  887.  
  888. ;;; Build list of words using ispell-look-command from dictionary
  889. ;;; ispell-look-dictionary (if this is a non-nil string).  Look for words
  890. ;;; starting with STRING if ispell-have-new-look is nil or look for REGEXP if
  891. ;;; ispell-have-new-look is t.  Returns result as an alist suitable for use by
  892. ;;; try-completion, all-completions, and completing-read.
  893. (defun ispell-lookup-build-list (string regexp)
  894.   (save-excursion
  895.     (message "Building list...")
  896.     (set-buffer (get-buffer-create " *ispell look*"))
  897.     (erase-buffer)
  898.  
  899.     (if (stringp ispell-look-dictionary)
  900.         (if ispell-have-new-look
  901.             (call-process ispell-look-command nil t nil "-fr" regexp
  902.                           ispell-look-dictionary)
  903.           (call-process ispell-look-command nil t nil "-f" string
  904.                         ispell-look-dictionary))
  905.       (if ispell-have-new-look
  906.           (call-process ispell-look-command nil t nil "-fr" regexp)
  907.         (call-process ispell-look-command nil t nil "-f" string)))
  908.  
  909.     ;; Build list for try-completion and all-completions by storing each line
  910.     ;; of output starting from bottom of buffer and deleting upwards.
  911.     (let (list)
  912.       (goto-char (point-min))
  913.       (while (not (= (point-min) (point-max)))
  914.         (end-of-line)
  915.         (setq list (cons (buffer-substring (point-min) (point)) list))
  916.         (forward-line)
  917.         (delete-region (point-min) (point)))
  918.  
  919.       ;; Clean.
  920.       (erase-buffer)
  921.       (message "Building list...done")
  922.  
  923.       ;; Make the list into an alist and return.
  924.       (mapcar 'list (nreverse list)))))
  925.  
  926. ;; Return regexp-quote of STRING if STRING is non-empty.
  927. ;; Otherwise return an unmatchable regexp.
  928. (defun ispell-non-empty-string (string)
  929.   (if (or (not string) (string-equal string ""))
  930.       "\\'\\`" ; An unmatchable string if string is null.
  931.     (regexp-quote string)))
  932.  
  933. (defvar ispell-message-cite-regexp "^   \\|^\t"
  934.   "*Regular expression to match lines cited from one message into another.")
  935.  
  936. ;;;###autoload
  937. (defun ispell-message ()
  938.   "Check the spelling of a mail message or news post.
  939. Don't check spelling of message headers or included messages.
  940.  
  941. To spell-check whenever a message is sent, include this line in .emacs:
  942.    (setq news-inews-hook (setq mail-send-hook 'ispell-message))
  943.  
  944. Or you can bind the function to C-c i in gnus or mail with:
  945.    (setq mail-mode-hook (setq news-reply-mode-hook
  946.     (function (lambda () (local-set-key \"\\C-ci\" 'ispell-message)))))"
  947.   (interactive)
  948.   (save-excursion
  949.     (let (non-internal-message
  950.       (old-case-fold-search case-fold-search)
  951.       (case-fold-search nil))
  952.       (goto-char (point-min))
  953.       ;; Don't spell-check the headers.
  954.       (if (search-forward mail-header-separator nil t)
  955.       ;; Move to first body line.
  956.       (forward-line 1)
  957.     (while (and (looking-at "[a-zA-Z-]+:\\|\t\\| ")
  958.             (not (eobp)))
  959.       (forward-line 1))
  960.     (setq non-internal-message t)
  961.     )
  962.       (let ((cite-regexp        ;Prefix of inserted text
  963.          (cond
  964.           ((featurep 'supercite)    ; sc 3.0
  965.            (concat "\\(" (sc-cite-regexp) "\\)" "\\|"
  966.                (ispell-non-empty-string sc-reference-tag-string)))
  967.           ((featurep 'sc)        ; sc 2.3
  968.            (concat "\\(" sc-cite-regexp "\\)" "\\|"
  969.                (ispell-non-empty-string sc-reference-tag-string)))
  970.           (non-internal-message    ; Assume nn sent us this message.
  971.            (concat "In [a-zA-Z.]+ you write:" "\\|"
  972.                "In <[^,;&+=]+> [^,;&+=]+ writes:" "\\|"
  973.                " *> *"))
  974.           ((equal major-mode 'news-reply-mode) ;Gnus
  975.            (concat "In article <" "\\|"
  976.                (if mail-yank-prefix
  977.                (ispell-non-empty-string mail-yank-prefix)
  978.              ispell-message-cite-regexp)))
  979.           ((boundp 'vm-included-text-prefix) ; VM mail message
  980.            (concat "[^,;&+=]+ writes:" "\\|"
  981.                (ispell-non-empty-string vm-included-text-prefix)
  982.                ))
  983.           ((boundp 'mh-ins-buf-prefix) ; mh mail message
  984.            (ispell-non-empty-string mh-ins-buf-prefix))
  985.           (mail-yank-prefix            ; vanilla mail message.
  986.            (ispell-non-empty-string mail-yank-prefix))
  987.           (t ispell-message-cite-regexp)))
  988.         (continue t))
  989.  
  990.     (while (and (not (eobp)) continue)
  991.       ;; Skip across text cited from other messages.
  992.       (while (and (looking-at (concat "^[ \t]*$\\|" cite-regexp))
  993.               (not (eobp)))
  994.         (forward-line 1))
  995.       (if (not (eobp))
  996.           ;; Check the next batch of lines that *aren't* cited.
  997.           (let ((start (point)))
  998.            (if (re-search-forward
  999.             (concat "^\\(" cite-regexp "\\)") nil 'end)
  1000.            (beginning-of-line))
  1001.         (let ((case-fold-search old-case-fold-search))
  1002.           (save-excursion
  1003.             (setq continue (ispell-region (- start 1) (point))))))))))))
  1004.  
  1005. (provide 'ispell)
  1006.  
  1007. ;;; ispell.el ends here
  1008.